using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

using VSTalk.Engine.Annotations;
using VSTalk.Engine.Core.ChatSession;
using VSTalk.Engine.Core.Context;
using VSTalk.Engine.Core.EntitySession;
using VSTalk.Engine.Core.EnvironmentManagers.UI;
using VSTalk.Engine.Core.Feed;
using VSTalk.Engine.Core.Feed.MessageState;
using VSTalk.Engine.Core.History;
using VSTalk.Engine.Core.XmppCommunication;
using VSTalk.Engine.Core.XmppCommunication.Services.MessageService;
using VSTalk.Engine.Core.XmppUtils;
using VSTalk.Engine.Utils;
using VSTalk.Engine.ViewModel.Messenger.History;
using VSTalk.Engine.ViewModel.Messenger.MessageBuilder;
using VSTalk.Engine.ViewModel.Messenger.ThreadRenderer;
using VSTalk.Engine.ViewModel.Messenger.ThreadRenderer.Command;
using VSTalk.Tools;
using VSTalk.Model;

namespace VSTalk.Engine.ViewModel.Messenger
{
    public class InterlocutorChatViewModel : IChatViewModel, INotifyPropertyChanged
    {
        private readonly IFrameViewModel _frame;

        private readonly Interlocutor _interlocutor;

        private RichTextBox _chatTextBox;

        private RichTextBox _messageTextBox;

        private readonly IList<IMessage> _pendingMessages = new List<IMessage>();

        private readonly ConversationThread _conversationThread;

        private HistoryLoaderViewModel _historyLoader;

        private IInterlocutorChatSession _chatSession;

        private ISubscription _frameUnit;
        private ISubscription _themeUnit;

        private static IModelContext ModelContext
        {
            get { return AppServices.Get<IModelContext>(); }
        }

        private static IMessageFeed MessageFeed
        {
            get { return AppServices.Get<IMessageFeed>(); }
        }

        private static ISessionModel SessionModel
        {
            get { return AppServices.Get<ISessionModel>(); }
        }

        private static IChatSessionFactory ChatSession
        {
            get { return AppServices.Get<IChatSessionFactory>(); }
        }

        private static IWindowManager WindowManager
        {
            get { return AppServices.Get<IWindowManager>(); }
        }

        private static IThemeManager ThemeManager
        {
            get { return WindowManager.ThemeManager; }
        }

        #region properties
        public Interlocutor Interlocutor
        {
            get { return _interlocutor; }
        }

        public RichTextBox ChatTextBox
        {
            get { return _chatTextBox; }
            set
            {
                _chatTextBox = value;
                PropertyChanged.Notify(() => ChatTextBox);
            }
        }

        public RichTextBox MessageTextBox
        {
            get { return _messageTextBox; }
            set
            {
                _messageTextBox = value;
                PropertyChanged.Notify(() => MessageTextBox);
            }
        }

        [UsedImplicitly]
        public RelayCommand PasteCallStack
        {
            get { return _frame.PasteCallStack; }
        }

        [UsedImplicitly]
        public RelayCommand PasteDebugOutput
        {
            get { return _frame.PasteDebugOutput; }
        }

        public RelayCommand LoadHistory { get; set; }

        #endregion

        public InterlocutorChatViewModel(IFrameViewModel frame, Interlocutor interlocutor)
        {
            _frame = frame;
            _interlocutor = interlocutor;

            var chatDocument = new FlowDocument();
            ChatTextBox = new RichTextBox(chatDocument);
            ChatTextBox.IsDocumentEnabled = true;
            ChatTextBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
            ChatTextBox.Background = Brushes.Transparent;
            ChatTextBox.IsReadOnly = true;
            _conversationThread = CreateThread(chatDocument);

            MessageTextBox = new RichTextBox(new FlowDocument());
            MessageTextBox.IsDocumentEnabled = true;
            MessageTextBox.Background = Brushes.Transparent;
            MessageTextBox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
            ParseCurrentHistory();
            SetTheme(ThemeManager.ActiveTheme);
            SetCommands();
            _historyLoader = CreateHistoryLoader();
       }

        private HistoryLoaderViewModel CreateHistoryLoader()
        {
            var client = ModelContext.GetClientByContact(_interlocutor);
            var loader = new HistoryLoaderViewModel(client.Id, XmppIdConverter.Jid(_interlocutor), _conversationThread);
            loader.Init();
            return loader;
        }

        private void SetCommands()
        {
            LoadHistory = new RelayCommand(LoadHistoryExecuted);
        }

        private void LoadHistoryExecuted()
        {
            var client = ModelContext.GetClientByContact(_interlocutor);
            var bare = XmppIdConverter.Jid(_interlocutor).Bare;
            var history = HistoryService.Read(client.Id, bare);
            ModifyTextBox(() =>
            {
                foreach (var message in history)
                {
                    var command = new RenderXmppMessage(_conversationThread.Model, message);
                    command.Execute();
                }
            });
        }

        private static IHistoryService HistoryService
        {
            get { return AppServices.Get<IHistoryService>(); }
        }

        public void StartListen()
        {
            _frameUnit = _frame.SubscribeToChange(() => _frame.CurrentChat, sender =>
            {
                if (_frame.CurrentChat == this)
                {
                    foreach (var pendingMessage in _pendingMessages)
                    {
                        var counter = GetCounter();
                        counter.MarkAsRead(pendingMessage);
                    }
                    _pendingMessages.Clear();
                    ChatTextBox.ScrollToEnd();
                }
            });

            _themeUnit = ThemeManager.SubscribeToChange(() => ThemeManager.ActiveTheme, ThemeChanged);
        }

        private void ThemeChanged(IThemeManager sender)
        {
            SetTheme(ThemeManager.ActiveTheme);
        }

        private void SetTheme(ThemeMode activeTheme)
        {
            MessageTextBox.CaretBrush = ChatTextBox.Document.Foreground = MessageTextBox.Document.Foreground = 
                activeTheme == ThemeMode.Dark
                ? Brushes.White
                : Brushes.Black;
        }

        public void StopListen()
        {
            _frameUnit.Unsubscribe();
            _themeUnit.Unsubscribe();
        }

        private ConversationThread CreateThread(FlowDocument document)
        {
            var thread = new ConversationThread(document);
            return thread;
        }

        protected void ParseCurrentHistory()
        {
            ModifyTextBox(() =>
            {
                foreach (var msg in MessageFeed.GetMessages(Predicate))
                {
                    var counter = GetCounter();
                    counter.MarkAsRead(msg);
                    var command = new RenderXmppMessage(_conversationThread.Model, msg);
                    command.Execute();
                }
            });
        }

        protected void ModifyTextBox(Action action)
        {
            ChatTextBox.BeginChange();
            action();
            ChatTextBox.EndChange();
            ChatTextBox.ScrollToEnd();
        }

        public object Model
        {
            get { return Interlocutor; }
        }

        public void SendMessage()
        {
            var chatSession = GetOrCreateSession();
            var messageBuilder = MessageBuilderFactory.Create(chatSession);
            var document = MessageTextBox.Document;
            var xmppMessage = messageBuilder.Create(document);

            var messageService = CommunicationServiceFactory.Create<MessageService>(Interlocutor);
            messageService.SendMessage(chatSession, xmppMessage);
            ClearMessage();
        }

        private IInterlocutorChatSession GetOrCreateSession()
        {
            return _chatSession ?? (_chatSession = ChatSession.Create(Interlocutor));
        }

        public bool CanSendMessage()
        {
            var client = ModelContext.GetClientByContact(Interlocutor);
            var context = SessionModel.GetClientContext(client);
            return context.State == ClientState.Connected &&
                   !IsMessageEmpty();

        }

        public void AppendToMessage(string str)
        {
            MessageTextBox.Document.Blocks.Add(new Paragraph(new Run(str)));
        }

        private void ClearMessage()
        {
            MessageTextBox.Document.Blocks.Clear();
        }

        private bool IsMessageEmpty()
        {
            return new TextRange(MessageTextBox.Document.ContentStart, MessageTextBox.Document.ContentEnd).IsEmpty;
        }

        public void Consume(IMessage message)
        {
            if (!_frame.IsActive || _frame.CurrentChat != this)
            {
                _pendingMessages.Add(message);
            }
            else if (_frame.CurrentChat == this)
            {
                var counter = GetCounter();
                counter.MarkAsRead(message);
            }

            ModifyTextBox(() =>
            {
                var command = new RenderXmppMessage(_conversationThread.Model, message);
                command.Execute();
            });
        }

        public UnreadMessageCounter GetCounter()
        {
            var stateManager = AppServices.Get<IMessageSateManager>();
            var counter = stateManager.GetCounter(_interlocutor);
            return counter;
        }

        public bool Predicate(IMessage message)
        {
            return AddressPatterns.From(_interlocutor)(message) ||
                   AddressPatterns.To(_interlocutor)(message);
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}